﻿using OneDriveRestAPI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CloudStorageLight.Core
{

    public class OneDriveBlobAdapter : BlobAdapterBase
    {
        string ClientId;
        string ClientSecret;
        string AccessToken;
        string RefreshToken;
        internal string onedriveFilePath;

        public OneDriveBlobAdapter(string rootPath, string accessToken, string refreshToken, string onedriveFilePath, string timezone = null)
            :this(rootPath, SystemSettings.Instance.OneDriveClientId, SystemSettings.Instance.OneDriveClientSecret,accessToken, refreshToken,onedriveFilePath, timezone)
        {

        }
        public OneDriveBlobAdapter(string rootPath, string clientId, string clientSecret, string accessToken, string refreshToken, string onedriveFilePath, string timezone)
        {
            this.RootPath = rootPath;
            this.ClientId = clientId;
            this.ClientSecret = clientSecret;
            this.AccessToken = accessToken;
            this.RefreshToken = refreshToken;
            this.onedriveFilePath = onedriveFilePath.TrimEnd('/') + "/";
            this.TimeZone = timezone;

        }

        public string ConvertDropPathToUrlPath(string ftpPath)
        {
            if (onedriveFilePath.Length > ftpPath.Length) return RootPath;
            var uriPath = ftpPath.Substring(onedriveFilePath.Length);
            uriPath = RootPath + uriPath;
            return uriPath;
        }

        public string ConvertUrlPathToOneDrivePath(string urlPath)
        {
            return this.onedriveFilePath + urlPath.Substring(RootPath.Length);
        }

        private Client GetOneDriveClient()
        {
            if (_client == null)
            {
                var options = new Options
                {
                    ClientId = ClientId,
                    ClientSecret = ClientSecret,
                    AutoRefreshTokens = true,
                    PrettyJson = false,
                    AccessToken = AccessToken,
                    RefreshToken = RefreshToken,

                };
                // Initialize a new Client (without an Access/Refresh tokens 
                _client = new Client(options);
            }


            return _client;
        }

        private Client _client;

        //private FtpClient GetFtpClient()
        //{
        //    if (clientInstance == null)
        //    {
        //        clientInstance = new FtpClient();
        //        clientInstance.Host = ftpUrl;
        //        clientInstance.Credentials = new System.Net.NetworkCredential(ftpUser, ftpPassword);
        //        clientInstance.EncryptionMode = ftps ? FtpEncryptionMode.Explicit : FtpEncryptionMode.None;
        //        clientInstance.ValidateCertificate += client_ValidateCertificate;
        //    }
        //    return clientInstance;
        //}
        //private DropNetClient clientInstance;

        //void client_ValidateCertificate(FtpClient control, FtpSslValidationEventArgs e)
        //{
        //    e.Accept = true;
        //}

        /// <summary>
        /// 指定したファイルが存在するか
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        protected override async Task<bool> ExistsAsyncInternal(string path)
        {
            return (await GetFileByUrlPathAsync(GetOneDriveClient(), path)) != null;
        }

        private Dictionary<string, OneDriveRestAPI.Model.File> fileCahce = new Dictionary<string, OneDriveRestAPI.Model.File>();

        string GetParentFolder(string path)
        {
            path = path.TrimEnd('/');
            return path.Substring(0, path.Length - path.Split('/').Last().Length);
        }

        string GetFileName(string path)
        {
            return path.Substring(path.Length - path.Split('/').Last().Length);
        }

        async Task<OneDriveRestAPI.Model.File> GetFileByUrlPathAsync(Client client, string path)
        {
            return await GetFileAsync(client, ConvertUrlPathToOneDrivePath(path));
        }


        async Task<OneDriveRestAPI.Model.File> GetFileAsync(Client client, string path)
        {
            if (fileCahce.ContainsKey(path)) return fileCahce[path];
            if (path == "/")
            {
                var f = await client.GetFolderAsync();
                fileCahce["/"] = f;
                return f;
            }
            var parent = GetParentFolder(path);
            if (parent == null) return null;
            var parentFolder = await GetFileAsync(client, parent);
            if (parentFolder == null) return null;
            var list = await client.GetContentsAsync(parentFolder.Id);
            foreach(var item in list)
            {
                fileCahce[parent + item.Name] = item;
            }
            var name = path.Substring(parent.Length).TrimEnd('/');
            return list.FirstOrDefault(x => x.Name == name);
        }

        /// <summary>
        /// Blobのコピー
        /// </summary>
        /// <param name="path"></param>
        /// <param name="fromUri"></param>
        /// <returns></returns>
        public override async Task CopyAsync(string path, BlobFile file)
        {
            var client = GetOneDriveClient();
            var oneFolderDest = await GetFileByUrlPathAsync(client, GetParentFolder(path));
            var oneFileSrc = await GetFileByUrlPathAsync(client, file.Path);
            if (oneFolderDest == null) oneFolderDest = await CreateDirectoryAsync(client, ConvertUrlPathToOneDrivePath(GetParentFolder(path)));
            var oneFile = await client.CopyAsync(oneFileSrc.Id, oneFolderDest.Id);
            var name = GetFileName(path);
            if (oneFile.Name != name) await client.RenameFileAsync(oneFile.Id, name);
        }

        private async Task<OneDriveRestAPI.Model.File> CreateDirectoryAsync(Client client, string folderPath)
        {
            var folder = await GetFileAsync(client, folderPath);
            if (folder != null) return folder;
            var parentPath = GetParentFolder(folderPath);
            folder = await CreateDirectoryAsync(client, parentPath);
            return await client.CreateFolderAsync(folder.Id, GetFileName(folderPath.TrimEnd('/')));             
        }

        /// <summary>
        /// 削除
        /// </summary>
        /// <returns></returns>
        public override async Task<bool> DeleteAsync(BlobItem file)
        {

            var client = GetOneDriveClient();
            var oneFile = await GetFileByUrlPathAsync(client, file.Path);
            if (oneFile == null) return false;
            await client.DeleteAsync(oneFile.Id);
            ClearExistsCahce();
            return true;

        }

        /// <summary>
        /// ディレクトリののファイル一覧を取得する
        /// </summary>
        /// <param name="id"></param>
        /// <param name="maxSize"></param>
        /// <returns></returns>
        public override async Task<List<BlobItem>> GetFilesAsync(string id, int maxSize, bool flat = false)
        {
            var client = GetOneDriveClient();
            var result = new List<BlobItem>();

            var logicalPath = id.StartsWith(RootPath) ? id : RootPath;
            await ListFilesAsync(logicalPath, maxSize, client, result, flat);
            return result;
        }

        private async Task ListFilesAsync(string urlPath, int maxSize, Client client, List<BlobItem> result, bool flat)
        {
            var folders = new List<BlobDirectory>();
            var oneFile = await GetFileByUrlPathAsync(client, urlPath);
            if (oneFile == null) return;
            foreach (var item in await client.GetContentsAsync(oneFile.Id))
            {
                BlobItem fileItem = null;
                string logicalPath;
                if (item.Type == OneDriveRestAPI.Model.Folder.FolderType)
                {
                    logicalPath = urlPath + item.Name + "/";
                    var folder = new BlobDirectory(Parent, logicalPath);
                    folders.Add(folder);
                    fileItem = folder;
                }
                else
                {
                    logicalPath = urlPath + item.Name;
                    fileItem = new BlobFile(Parent, logicalPath);
                    fileItem.Length = item.Size;                    
                    fileItem.LastModified = ConvertTimezone(DateTime.Parse(item.Updated_Time));
                }
                var onePath = ConvertUrlPathToOneDrivePath(logicalPath);
                fileCahce[onePath] = item;

                if (fileItem != null) result.Add(fileItem);
                if (maxSize > 0 && result.Count >= maxSize)
                {
                    break;
                }
            }

            if (flat)
            {
                foreach (var folder in folders)
                {
                    await ListFilesAsync(folder.Path,  maxSize, client, result, flat);
                    if (maxSize > 0 && result.Count >= maxSize)
                    {
                        break;
                    }
                }
            }
        }


        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task UploadAsync(BlobItem item, Stream stream)
        {
            var client = GetOneDriveClient();
            var oneFolderPath = GetParentFolder(item.Path);
            var oneFolder = await GetFileByUrlPathAsync(client, oneFolderPath);
            await client.UploadAsync(oneFolder.Id, stream, item.Name, OneDriveRestAPI.Model.OverwriteOption.Overwrite, false, true);
            ClearExistsCahce();
        }

        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        public override async Task UploadAsync(BlobItem item, string localpath)
        {
            using (var st = new FileStream(localpath, FileMode.Open))
            {
                await UploadAsync(item, st);
            }
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, Stream stream)
        {
            var client = GetOneDriveClient();
            var oneFile = await GetFileByUrlPathAsync(client, item.Path);
            using(var st = await client.DownloadAsync(oneFile.Id))
            {
                await st.CopyToAsync(stream);
            }
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, string localpath)
        {
            using (var st = new FileStream(localpath, FileMode.Create))
            {
                await DownloadAsync(item, st);
            }
        }

        /// <summary>
        /// フォルダ作成
        /// </summary>
        /// <param name="dire"></param>
        /// <returns></returns>
        public override async Task CreateDirectoryAsync(BlobDirectory dire)
        {

            var client = GetOneDriveClient();
            await CreateDirectoryAsync(client, ConvertUrlPathToOneDrivePath(dire.Path));
            ClearExistsCahce();

        }

        /// <summary>
        /// メタ情報の設定
        /// </summary>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task SetMetadataAsync(BlobItem item)
        {
            item.Link = ConvertUrlPathToOneDrivePath(item.Path);

        }
        #pragma warning restore 1998

        /// <summary>
        /// メタ情報の取得
        /// </summary>
        /// <param name="fetch"></param>
        /// <returns></returns>
        public override async Task GetMetadataAsync(BlobItem file)
        {
            var client = GetOneDriveClient();
            var oneFile = await GetFileByUrlPathAsync(client, file.Path);
            if (oneFile == null) return;
            file.LastModified = ConvertTimezone(DateTime.Parse(oneFile.Updated_Time));
            file.Length = oneFile.Size;

        }

        /// <summary>
        /// 後処理
        /// </summary>
        public override void Dispose()
        {

        }
    }

}
